home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-02 / objovr.zip / OBJOVR.DOC < prev    next >
Text File  |  1993-01-04  |  15KB  |  436 lines

  1. David Dubois [71401,747]
  2. Zelkop Software
  3. 1988.10.18
  4.  
  5. 01/10/90  Fixed bug in the "overlay detector" 
  6.           Ron Schuster [76666,2322]
  7.  
  8. OBJOVR
  9. ======
  10.  
  11. Description of a technique for accessing "OBJitized" data within an
  12. overlayed unit. By OBJitized data, I mean data which has been
  13. converted to an .OBJ file using the BinObj utility.
  14.  
  15. The Turbo Pascal documentation shows how to use BinObj to link data
  16. directly into your program. It also suggests that such data shouldn't
  17. be overlayed. Here, I will show how to get around that limitation. If
  18. you haven't read the documentation about BinObj, you should do so
  19. before reading this.
  20.  
  21. I will demonstrate the technique through a simple example.
  22.  
  23. Files
  24. =====
  25.  
  26.   ObjOvr.Doc  This documentation
  27.  MakeOBJ.Pas  Program to generate example .OBJ file
  28.  LookOBJ.Pas  The unit with OBJitized data
  29.   UseOBJ.Pas  The program that uses the unit
  30. OBJTypes.Pas  A supporting unit
  31. InitOver.Pas  Another supporting unit
  32.  
  33. How to run the example.
  34. =======================
  35.  
  36. First, compile and run MakeOBJ. This creates the .OBJ file that will
  37. be linked in. It uses the DOS Exec routine to make the appropriate
  38. call to BinObj. You may have to change the path in the EXEC call.
  39.  
  40. Then, compile and run UseOBJ. Since UseOBJ contains an overlayed file,
  41. it must be compiled to disk. Upon running the program, you see that
  42. unit LookOBJ has detected that it is overlayed. The program then
  43. prints out the OBJitized data, which happens to be the square roots of
  44. the numbers 1 through 100.
  45.  
  46. The program will work whether or not the unit is overlayed. To see
  47. this, insert a space between "{" and "$O LookOBJ}". This will cause
  48. the overlay directive to be ignored. Compile and run the program
  49. again. Neither a Make nor Build will be necessary. Note that the
  50. LookOBJ unit "knows" that it is no longer overlayed.
  51.  
  52. What's the problem?
  53. ===================
  54.  
  55. Why is it necessary to do anything fancy? Why is referencing OBJitized
  56. data in an overlayed unit any different? First, you have to understand
  57. a little about how the overlay manager works.
  58.  
  59. An overlayed unit lives a double life. On the one hand, it consists of
  60. code that is relocatable. That is, each time a routine is called, it
  61. may be in a different place in memory. On the other hand, other
  62. routines have to be able to find the overlayed routines in order to
  63. call them, even if they aren't currently loaded in memory.
  64.  
  65. In effect an overlayed unit has two different segments. One of these
  66. segments is small and permanent. This segment is part of the .EXE
  67. file. Once the .EXE file is loaded, this segment doesn't move. It
  68. consists of small chunk of code for each public routine in the
  69. overlayed unit plus a little overhead. This is the segment that is
  70. shown in a .MAP file.
  71.  
  72. The routines themselves will be in another segment. This segment may
  73. be anywhere and may change as the overlay manager loads and re-loads
  74. the unit. Of course, if the unit is not currently loaded, then it is
  75. in no segment at all.
  76.  
  77. Whenever you make a call to a routine in the overlayed unit, or refer
  78. to its address, (either by using @, or by storing it in a procedural
  79. variable, or passing it as a parameter), you are referring to first,
  80. permanent segment. The code there, in turn, refers to the actual code,
  81. should it be loaded.
  82.  
  83. So, what happens when you use @ to refer to data that has been
  84. OBJitized? Let's look at an example.
  85.  
  86.         procedure DataLink;
  87.         external;
  88.         {$L DataLink.OBJ}
  89.  
  90. What is the value of "@ DataLink"? Well, it depends on whether it
  91. resides in an overlayed unit. If the unit is not overlayed, then
  92. @ DataLink will be the address of the actual data. If the unit is
  93. overlayed, it will be the address of a small chunk of code in the
  94. permanent segment. In order to find the data, we have to know a little
  95. more about that chunk.
  96.  
  97. How do you find the data?
  98. =========================
  99.  
  100. That chunk will be one of two things. When the overlayed unit is not
  101. currently loaded, it will consist of an interrupt call, INT $3F.
  102. Interrupt $3F is handled by the overlay manager. When the unit is
  103. loaded, those interrupt calls are replaced with FAR CALLs to the
  104. appropriate place in memory. The far-called segment will depend on
  105. where the unit was loaded. When the unit is un-loaded, the interrupt
  106. calls re-appear.
  107.  
  108. So in our example above, if the unit is overlayed, and the unit has
  109. been loaded, then @ DataLink is the address of the FAR CALL. That
  110. call, in turn, points to the data that we are looking for.
  111.  
  112. Please note that this depends on whether the unit is actually
  113. overlayed, regardless of whether it was compiled with the $O+
  114. (overlays allowed) option activated. If the unit has been compiled so
  115. that it CAN be overlayed, (i.e., $O+ is on), but is not ACTUALLY
  116. overlayed, (i.e. the {$O UnitName} directive was not given), then @
  117. DataLink will refer to the actual data.
  118.  
  119. Therefore, in order to decide how to handle itself, a unit has to be
  120. able to determine whether it has been overlayed. This can be
  121. determined by looking at offset 0 of the unit's segment. If the unit
  122. is overlayed, then the segment will be that special permanent segment
  123. that I have referred to. It so happens that, if the segment is
  124. overlayed, that will always begin with an INT $3F instruction.  Therefore,
  125. if you consider that to be a Word, the number $3FCD is stored there.      
  126.  
  127. Note that this is not fool-proof. It is possible that a non-overlayed
  128. unit may have that special number at that particular location. This
  129. would occur in the unlikely case that the program had range-checking
  130. turned on, and the first range declared in the unit had a lower bound
  131. having $3FCD as its least significant 16-bits.  This would seem unlikely  
  132. unless a malicious attempt is made by the programmer to confuse the           
  133. "overlay detector".
  134.  
  135. [David's original "overlay detector" also checked whether the SECOND
  136. word of the segment was equal to zero.  Although word usually appears
  137. to contain a zero, it is not always.  This would cause strange and
  138. difficult-to-find bugs.  See the file OVRLAY.TXT (CompuServe BPROGA
  139. forum LIB 2) for more details. -- RJS]
  140.  
  141. So, with this knowledge well in hand, we are ready to face the
  142. example. Here is the code with detailed documentation:
  143.  
  144. Details, details
  145. ================
  146.  
  147. OBJTypes
  148. --------
  149.  
  150. {For the purposes of this example, the OBJitized data will consist of
  151. an array of 100 reals. This type must be accessed from several places,
  152. so I put it into a unit by itself.}
  153.  
  154. unit ObjTypes;
  155.  
  156. interface
  157.  
  158.   const
  159.     Max = 100;
  160.   type
  161.     OBJitizedDataType = array [ 1 .. Max ] of real;
  162.  
  163. implementation
  164.  
  165. end.
  166.  
  167. MakeOBJ
  168. -------
  169.  
  170. {MakeOBJ is a stand-alone program that generates the .OBJ file that
  171. will be linked into our example program. The data consists of the
  172. square roots of the numbers 1 through 100.}
  173.  
  174. {MakeOBJ is going to make a call to the DOS Exec routine, so, we must
  175. make sure we leave room for our spawned program to run. Here we
  176. allocate the minimum stack size, and no heap.}
  177.  
  178. {$M 1024,0,0}
  179.  
  180. {DOS provides access to the Exec routine, while OBJTypes provides
  181. access to OBJitizedDataType, (as well as Max.)}
  182.  
  183. uses
  184.   DOS, OBJTypes;
  185.  
  186. {We will fill OBJitizedData with the data we want to OBJitize. Then
  187. we will write it to BinaryDataFile.}
  188.  
  189. var
  190.   OBJitizedData  : OBJitizedDataType;
  191.   BinaryDataFile : file of OBJitizedDataType;
  192.   I              : integer;
  193.  
  194. begin
  195.   writeln ( 'MakeOBJ' );
  196.   writeln;
  197.   writeln ( 'Generating data to OBJitize ...' );
  198.  
  199.   for I := 1 to Max do
  200.     OBJitizedData [ I ] := sqrt ( I );
  201.  
  202.   writeln ( 'Creating data to file ...' );
  203.  
  204.   assign  ( BinaryDataFile, 'OBJitize.DAT' );
  205.   rewrite ( BinaryDataFile                 );
  206.   write   ( BinaryDataFile, OBJitizedData  );
  207.   close   ( BinaryDataFile                 );
  208.  
  209.   writeln ( 'Executing BinOBJ ...' );
  210.  
  211.   {The code above created the file OBJitize.DAT. The BinObj utility
  212.   will be used to convert that to an .OBJ file that can be linked into
  213.   a program. OBJitizedDataLink is the identifier that must be used in
  214.   the program. This assumes that BinOBJ will be found in the path
  215.   "c:\TP\". If yours is elsewhere, you will have to change this.}
  216.  
  217.   Exec ( 'c:\TP\BinOBJ.EXE', 'OBJitize.DAT OBJitize.OBJ OBJitizedDataLink' );
  218. end.
  219.  
  220. InitOver
  221. --------
  222.  
  223. {Our example unit will have an initialization section. Therefore, I
  224. have to have a unit that will initialize the overlay manager, before
  225. the example unit is initialized. See the Turbo Pascal documentation
  226. referring to the standard Overlay unit.}
  227.  
  228. unit InitOver;
  229.  
  230. interface
  231.  
  232.   uses
  233.     Overlay;
  234.  
  235. implementation
  236.  
  237. begin
  238.   OvrInit ( 'UseOBJ.OVR' );
  239. end.
  240.  
  241. LookOBJ
  242. -------
  243.  
  244. {We will turn on the overlays-allowed switch. This will not force the
  245. unit to be overlayed, but makes it possible. }
  246.  
  247. {$O+}
  248.  
  249. unit LookOBJ;
  250.  
  251. interface
  252.  
  253.   {This function returns the Ith component of our example data. So
  254.   calling OBJitized ( 16 ) will return the 16th component of our
  255.   array, which happens to be 4.0.}
  256.  
  257.   function OBJitized ( I : integer ) : real;
  258.  
  259. implementation
  260.  
  261. uses
  262.   OBJTypes;
  263.  
  264. {This unit will detect whether or not it has been overlayed. It stores
  265. the result here.}
  266.  
  267. var
  268.   ThisUnitIsOverlayed : boolean;
  269.  
  270. {For the purposes of using this technique, we will require a pointer
  271. to the data, as well as a pointer to a pointer to the data.}
  272.  
  273. type
  274.   OBJitizedDataPtr    = ^ OBJitizedDataType;
  275.   OBJitizedDataPtrPtr = ^ OBJitizedDataPtr;
  276.  
  277.   {Here is the OBJitizedData.}
  278.  
  279.   procedure OBJitizedDataLink;
  280.   external;
  281.   {$L OBJitize.OBJ }
  282.  
  283.   {Here is the procedure that figures out where the OBJitized data is.
  284.   It returns a pointer to the data. It assumes that
  285.   ThisUnitIsOverlayed has already been set correctly. If the unit has
  286.   not been overlayed, then finding the data is a simple task. Just
  287.   take the address of OBJitizedDataLink. If the unit is overlayed,
  288.   then we havfe a tougher problem. In this case
  289.  
  290.                          @ OBJitizedDataLink
  291.  
  292.   is not a pointer to the data at all. Instead, it a pointer to a FAR
  293.   CALL. The far call consists of one byte, an $EA as it happens,
  294.   followed by a four-byte segment-offset pair. That address is the
  295.   address of the data we are looking for. To skip over the one byte, I
  296.   convert the address into a number, add one, and then convert it back
  297.   into a pointer again.
  298.  
  299.                     longint ( @ OBJitizedDataLink )
  300.                succ ( longint ( @ OBJitizedDataLink ) )
  301.     OBJitizedDataPtrPtr ( succ ( longint ( @ OBJitizedDataLink ) ) )
  302.  
  303.   That points to the operand of the far call. When de-referenced, we
  304.   have the pointer to the data.
  305.  
  306.   Note, that will only work if the unit containing OBJitiziedDataLink
  307.   is currently loaded. We know that it loaded since this function
  308.   occupies the same unit.}
  309.  
  310.   function OBJitizedData : OBJitizedDataPtr;
  311.   begin
  312.     if ThisUnitIsOverlayed then
  313.       OBJitizedData := OBJitizedDataPtrPtr (
  314.                          succ ( longint ( @ OBJitizedDataLink ) ) ) ^
  315.     else
  316.       OBJitizedData := @ OBJitizedDataLink;
  317.   end;
  318.  
  319.   {Here is the function that refers to the data. It uses OBJitizedData
  320.   to find the data, and simply de-references the pointer it returns to
  321.   find the data from the .OBJ file. In this case, it returns a
  322.   component from the array.}
  323.  
  324.   function OBJitized ( I : integer ) : real;
  325.   begin
  326.     OBJitized := OBJitizedData ^ [ I ];
  327.   end;
  328.  
  329. {Here is the intialization code. This is where the unit finds out
  330. whether it has been overlayed or not. Here we have to find offset 0 of
  331. that small, permanent segment that refers to this unit. To do that, I
  332. start with the address of OBJitizedDataLink.
  333.  
  334.                         @ OBJitizedDataLink
  335.  
  336. This could have been any routine in the unit. I clear the offset
  337. portion of this address by converting it a longint, masking out the
  338. offset using a logical and, then converting it back to a pointer
  339. again.
  340.  
  341.                   longint ( @ OBJitizedDataLink )
  342.              $FFFF0000 and longint ( @ OBJitizedDataLink ))
  343.        WordPtr ( $FFFF0000 and longint ( @ OBJitizedDataLink ) )
  344.  
  345. De-referencing that pointer gives the first 2 bytes of that segment.      
  346. If the unit was overlayed that should be the longint $3FCD, which         
  347. is an INT $3F instruction.}                                               
  348.  
  349. type
  350.   WordPtr = ^ word;
  351. begin
  352.   ThisUnitIsOverlayed :=   WordPtr (    $FFFF0000
  353.                                   and longint ( @ OBJitizedDataLink ) ) ^
  354.                          = $3FCD;
  355.  
  356.   {For the purposes of this example, the result is reported.}
  357.  
  358.   writeln ( 'This unit is overlayed: ', ThisUnitIsOverlayed );
  359. end.
  360.  
  361.  
  362. UseOBJ
  363. ------
  364.  
  365. {Finally, the program that puts it all together.
  366.  
  367. Far calls should be forced on when using overlays.}
  368.  
  369. {$F+}
  370.  
  371. program UseOBJ;
  372.  
  373. {InitOver allows the overlay manager to be initialized before LookOBJ
  374. is. OBJTypes provides access to Max. LookOBJ does all the work.}
  375.  
  376. uses
  377.   InitOver, OBJTypes, LookOBJ;
  378.  
  379. {We will overlay LookOBJ. You can disable this directive by placing a
  380. space before the dollar sign.}
  381.  
  382. {$O LookOBJ }
  383.  
  384. {The program simply writes out the values stored in the OBJitized data
  385. by making calls to the LookOBJ unit.}
  386.  
  387. var
  388.   I : integer;
  389. begin
  390.   for I := 1 to Max do
  391.     write ( OBJitized ( I ) : 16 : 10 );
  392. end.
  393.  
  394. Notes
  395. =====
  396.  
  397. The code that LookOBJ uses to determine whether it is in a unit can be
  398. moved out of the initialization section, and into the OBJitizedData
  399. routine. This will save code and data, and forego the confusion
  400. related to initalizing overlayed segments. But then, it will have to
  401. be run each time the data is accessed.
  402.  
  403. The code that determines the address of the data, in OBJitizedData,
  404. CANNOT be put into the initialization section. Since the data is
  405. relocatable, it may theoretically be in a different place each time it
  406. is accessed, and so its address must found anew each time.
  407.  
  408. The code for finding the address of the data, and the code that
  409. accesses the data using that address, must be in the same unit as the
  410. data. It would be pointless to find the address of the data, and then
  411. un-load the data in order to load the code that accesses it. Remember,
  412. once control returns outside the unit in which the data resides, all
  413. bets are off.
  414.  
  415. I am
  416. ====
  417.  
  418. David Neal Dubois
  419.  
  420. You can send mail to:
  421.  
  422. Zelkop Software
  423. P.O. Box 5177
  424. Armdale, Nova Scotia
  425. Canada
  426. B3L 4M7
  427.  
  428. My CompuServe User ID is [71401,747]. You can EasyPlex me, or leave a
  429. message on the BPROGA Borland's programmer's forum.
  430.  
  431. I am keen to receive any comments or suggestions.
  432.  
  433. I am releasing this knowledge to the public domain, but if you make
  434. use of it, please mention my name.
  435.  
  436.